#!/usr/local/bin/perl
package TNV;
use strict; use warnings;
no if $] >= 5.018, warnings => "experimental::smartmatch";
use feature qw/switch/;

use Glib qw/TRUE FALSE/;
use Gtk2;
use Gtk2::Gdk::Keysyms;

#-------------------------------------------------------------------

# widgets
my $win;
my ($vbox,$hbox);
my ($scr,$adj);
my $twin;
my $lsta;
my $led;
my $sbox;

# wparams
my $scrtype=-1; # [0-1]:hscr,vscr
my $scrcbid;
my $lstaon=1;
my ($stdgc,$selgc,$invselgc);
my ($bgc,$sbgc);
my $maximized;
my $fullscreen;
my $above;

# image data
my $pfiles;
my $pstats;
my $pthums;
my $pasp;
my $pnsel;
my $ctid;
my $pkf;
my ($pA,$pB);
my $gidon=0;

# geometry
my ($thumw,$thumh);
my ($xgap,$ygap)=(4,4); # <<<
my $thumw_def=128;
my ($thumw0,$thumh0);
my ($ftw,$fth);
my ($nx,$ny);
my ($ypos,$xpos,$zpos);

# aliases
my ($pft1,$pft2);
my ($s1,$s2);
my ($pn1,$pn2);
my ($ptd1,$ptd2);
my ($x,$y);
my ($px,$py);
my ($xx,$yy);
my ($pxx,$pyy);

# misc
my $scale;
my $pdscale; # external
my ($selk,$lensel);
my $nfold;
my $didqr=qr/^(\d+):/;
my $gidqr=qr/^.{1,1}:(\d+)/;
my $pmode;
my $fontw;
my $pfont;

# idle que
my @idleq;
my $wqid;
my $icbid;

# externals
my $pshow_image;
my $pconfig_cb;
my $pfin;
my $pmv;
my $prm;
my $psbar;
my $pkey;
my $pgetfn;
my $ppop;
my $powh;
my $pngp;
my $psep;
#$# my $praize;

# key-event
my ($shft,$ctrl,$meta);
my $pkeyin;

# mouse-event
my $clicked;
my $isdrag;
my $apos;

# etc
my $owhqr=qr/^\d:\d+:(\d*):(\d*)/;
my @leds;

#-------------------------------------------------------------------

sub tnv_fnc
{
    given($_[0]){ # init_tnv
	when(0){
	    my ($pps,$pes);
	    $pps              = $_[1];
	    $pes              = $_[2];
	    # params
	    $pfiles           = $pps->[0];
	    $pstats           = $pps->[1];
	    $pthums           = $pps->[2];
	    $pasp             = $pps->[3];
	    $pnsel            = $pps->[4];
	    $pdscale          = $pps->[5];
	    $pkf              = $pps->[6]; # <<<<
	    $pkeyin           = $pps->[7]; # <<<<
	    $pmode            = $pps->[8];
	    ($pA,$pB)         = ($pps->[9],$pps->[10]);
	    $pfont            = $pps->[11];
	    # externals
	    $pshow_image      = $pes->[0];
	    $pconfig_cb       = $pes->[1];
	    $pmv              = $pes->[2];
	    $prm              = $pes->[3];
	    $psbar            = $pes->[4];
	    $pkey             = $pes->[5];
	    $pgetfn           = $pes->[6];
	    $ppop             = $pes->[7];
	    $powh             = $pes->[8];
	    $pngp             = $pes->[9];
	    $psep             = $pes->[10];
	    $pfin             = $pes->[11];
#$#	    $praize           = $pes->[12];
	    #
	    $nfold=@$pstats;
	    $wqid=-1;
	    &set_geom();
	}
	when(1){ # show_tnv
	    $twin->grab_focus;
	    $ctid=$_[1] unless defined $ctid;
	    &set_lsta;
	}
	when(2){ # get_selected
	    return ($ctid,$selk,$lensel);
	}
	when(3){
	    return ($ftw,$fth);
	}
	when(4){ # grab_focus
	    return $twin->has_focus unless $_[1];
	    $twin->grab_focus;
	}
	when(5){
	    return $twin->has_focus;
	}
	when(6){ # get/set $ctid
	    $ctid=$_[1] if defined $_[1];
	    undef $ctid if $ctid<0;
 	    return $ctid;
	}
    }
}

sub set_geom
{
    $scale=1;
    if(defined($_[0])&&defined($_[1])){
	$thumw0=$_[0];
	$thumh0=$_[1];
    }elsif(defined($_[0])){
	$thumh0=int($_[0]/($$pasp>0?$$pasp:1));
    }elsif(defined($_[1])){
	$thumw0=int($_[1]*($$pasp>0?$$pasp:1));
    }else{
	$thumw0=$thumw_def;
	$thumh0=int($thumw0/($$pasp>0?$$pasp:1));
    }
    ($thumw,$thumh)=($thumw0,$thumh0);
    ($ftw,$fth)=($thumw+2*$xgap,$thumh+2*$ygap);
    $fontw=$ftw/8; # <<<<
}

sub mk_tnv
{
#    my ($scrtype,$bgcol,$sbgcol,$pixdir)=@_
    unless($twin){
	$bgc =Gtk2::Gdk::Color->parse($_[1]) if defined $_[1];
	$sbgc=Gtk2::Gdk::Color->parse($_[2]) if defined $_[2];
        #
	$lsta=Gtk2::Entry->new;
	$lsta->can_focus(FALSE);
	$lsta->set_editable(FALSE);
	undef $led;
	undef $sbox;
	eval{
	    my $i=-1;
	    foreach("gray.png","red.png","green.png","blue.png"){
		$leds[++$i]=Gtk2::Gdk::Pixbuf->new_from_file_at_scale(
		    "$_[3]/$_", 18, 18,1); # <<<<
	    }
	};
	unless($@){
	    $led=Gtk2::Image->new;
	    $sbox=Gtk2::HBox->new;
	    $led->set_from_pixbuf($leds[3]);
	    $sbox->pack_start($led,0,0,0);
	    $sbox->pack_start($lsta,1,1,0);
	}
	#------------------------------------------------------------------
	$twin=Gtk2::DrawingArea->new;
	$twin->modify_bg("normal",$bgc) if defined $bgc;
	$twin->can_focus(TRUE);
	$twin->add_events(
	    [qw/button_press_mask button_release_mask scroll-mask pointer-motion-mask/]);
	$twin->signal_connect(expose_event       => \&expose_cb);
	$twin->signal_connect(configure_event    => \&configure_cb);
	$twin->signal_connect(button_press_event => \&butpre_cb);
	$twin->signal_connect(button_release_event => \&butrel_cb);
	$twin->signal_connect(motion_notify_event => \&motion_notify_event);
	$twin->signal_connect(scroll_event       => \&wheel_cb);
	$twin->signal_connect(focus_in_event     => \&focus_cb);
	$twin->signal_connect(focus_out_event    => \&focus_cb);
	#------------------------------------------------------------------
    }

    # this will cause $vbox->allocation->width=1 to be passed to &$pconfig
    #    goto FIN if($scrtype==$_[0] && defined $vbox);

    if($hbox){
	if($vbox){
	    $vbox->remove($hbox);
	    $vbox->remove($sbox?$sbox:$lsta);
	}
	$hbox->remove($scr);
	$hbox->remove($twin);
	undef $hbox;
    }elsif($vbox){
	$vbox->remove($scr);
	$vbox->remove($twin);
	$vbox->remove($sbox?$sbox:$lsta);
    }
    undef $scr;
    given($_[0]){
	when([1,3]){
	    $scr=Gtk2::VScrollbar->new;
	    $adj=$scr->get_adjustment;
	    $hbox=Gtk2::HBox->new;
	    $hbox->pack_start($twin,1,1,0) if $_==1;
	    $hbox->pack_start($scr,0,0,0);
	    $hbox->pack_start($twin,1,1,0) if $_==3;
	    $vbox=Gtk2::VBox->new unless defined $vbox;
	    $vbox->pack_start($sbox?$sbox:$lsta,0,0,0);
	    $vbox->pack_start($hbox,1,1,0);
	    $zpos        =\$ypos;
	    ($pft1,$pft2)=(\$ftw,\$fth);
	    ($s1,$s2)    =(0,1);
	    ($pn1,$pn2)  =(\$nx,\$ny);
	    ($ptd1,$ptd2)=(\$thumw,\$thumh);
	    ($px,$py)    =(\$x,\$y);
	    ($pxx,$pyy)  =(\$xx,\$yy);
	}
	when([2,4]){
	    $scr=Gtk2::HScrollbar->new;
	    $adj=$scr->get_adjustment;
	    $vbox=Gtk2::VBox->new unless defined $vbox;
	    $vbox->pack_start($sbox?$sbox:$lsta,0,0,0);
	    $vbox->pack_start($scr,0,0,0) if $_==4;
	    $vbox->pack_start($twin,1,1,0);
	    $vbox->pack_start($scr,0,0,0) if $_==2;
	    $zpos        =\$xpos;
	    ($pft1,$pft2)=(\$fth,\$ftw);
	    ($s1,$s2)    =(1,0);
	    ($pn1,$pn2)  =(\$ny,\$nx);
	    ($ptd1,$ptd2)=(\$thumh,\$thumw);
	    ($px,$py)    =(\$y,\$x);
	    ($pxx,$pyy)  =(\$yy,\$xx);
	}
    }
  FIN:
    $$zpos=0;
    $scrtype=$_[0];
    undef $$pn1; # marks is_first
    undef $$pn2;
    $scrcbid=$adj->signal_connect(value_changed => \&scroll_cb);
    return \$vbox;
}

sub detach_tnv
{
    $adj->signal_handler_disconnect($scrcbid) if(defined $scrcbid && defined $adj);
    undef $scrcbid;
    return unless $win;
    return if $_[0];
    $win->hide;
    $win->remove($vbox);
    undef $win;
}

sub show_tnv
{
    my $pb=$_[0];
    $win =Gtk2::Window->new;
    $maximized=0;
    $fullscreen=0;
    $above=0;
    $win->add($$pb);
    $win->signal_connect( destroy => \&fin);
    $win->signal_connect( delete_event => sub{$win->destroy;});
    $win->signal_connect(key_press_event => \&keypre_cb);
    $win->signal_connect(window_state_event => \&ws_event);
    my $bw=$win->get_border_width;
    $win->set_default_size($ftw*4+2*$bw,
			   $fth*2+2*$bw+32);#<<< aprox
    $win->set_title("tnv4imv");
    $win->set_icon_list(@_[2..$#_]);
    $ctid=$_[1] if defined $_[1];
    $win->show_all;
}

sub fin
{
    &quit_idle if $icbid;
    $win->hide;
    $win->remove($vbox);
    undef $win;
    $$pmode=-1;
    return FALSE;
}

sub focus_cb
{
    return unless $led;
    $led->set_from_pixbuf($leds[$twin->has_focus?3:0]);
}

sub keypre_cb
{
    my ($w,$e) = @_;
    my $k=$e->keyval;
    given($k){
	when([$Gtk2::Gdk::Keysyms{Shift_L},
	      $Gtk2::Gdk::Keysyms{Shift_R},
	      $Gtk2::Gdk::Keysyms{Control_L},
	      $Gtk2::Gdk::Keysyms{Control_R},
	      $Gtk2::Gdk::Keysyms{Alt_L},
	      $Gtk2::Gdk::Keysyms{Alt_R}]){
	    return TRUE;
	}
    }
    my $s=($e->get_state >= ['shift-mask'])?1:0;
    my $c=($e->get_state >= ['control-mask'])?1:0;
    my $m=($e->get_state >= ['mod1-mask'])?1:0;
    return &$pkey($k,$s,$c,$m,2)
	unless (!defined($$pkeyin) && &proc_key($k,$s,$c,$m,undef));
}

sub proc_key
{
    my ($key,$e);
    ($key,$shft,$ctrl,$meta,$e)=@_;
    return FALSE if(defined($e)&&!$twin->has_focus);
    if(!$ctrl&&!$meta){
	given($key){
	    when($Gtk2::Gdk::Keysyms{Up}){
		if($scrtype & 1){
		    &set_ctid($ctid-$nx,undef,1);
		}else{
		    &set_ctid($ctid-1,undef,1);
		}
	    }
	    when($Gtk2::Gdk::Keysyms{Down}){
		if($scrtype & 1){
		    &set_ctid($ctid+$nx,undef,1);
		}else{
		    &set_ctid($ctid+1,undef,1);
		}
	    }
	    when($Gtk2::Gdk::Keysyms{Right}){
		if($scrtype & 1){
		    &set_ctid($ctid+1,undef,1);
		}else{
		    &set_ctid($ctid+$ny,undef,1);
		}
	    }
	    when($Gtk2::Gdk::Keysyms{Left}){
		if($scrtype & 1){
		    &set_ctid($ctid-1,undef,1);
		}else{
		    &set_ctid($ctid-$ny,undef,1);
		}
	    }

	    when($Gtk2::Gdk::Keysyms{d}){
		if(defined $selk){ # mv some, do not warp
		    my ($k1,$k2)=($lensel>=0)?($selk,$selk+$lensel)
			:($selk+$lensel,$selk);
		    if($k2<=$#$pstats){
			for(my $k=$k2;$k>=$k1;$k--){
			    &$pmv(1,$k);
			}
			if($k1<=$$pkf&&$$pkf<=$k2){
			    $$pkf++;
			}elsif($$pkf-1==$k2){
			    $$pkf-=$lensel+1;
			}
			$selk++;
			$shft=1; # <<<
			&set_ctid($ctid+1,1);
		    }
		}else{ # mv one, can warp
		    if($$pkf==$ctid){
			$$pkf=$ctid==$#$pstats?0:$$pkf+1;
		    }elsif($$pkf-1==$ctid){
			$$pkf--;
		    }elsif($$pkf==0&&$ctid==$#$pstats){
			$$pkf=$#$pstats;
		    }
		    &set_ctid(&$pmv(1,$ctid),1);
		}
	    }
	    when($Gtk2::Gdk::Keysyms{u}){
		if(defined $selk){
		    my ($k1,$k2)=($lensel>=0)?($selk,$selk+$lensel)
			:($selk+$lensel,$selk);
		    if($k1>=1){
			for(my $k=$k1;$k<=$k2;$k++){
			    &$pmv(-1,$k);
			}
			if($k1<=$$pkf&&$$pkf<=$k2){
			    $$pkf--;
			}elsif($$pkf+1==$k1){
			    $$pkf+=$lensel+1;
			}
			$selk--;
			$shft=1; # <<<
			&set_ctid($ctid-1,1);
		    }
		}else{
		    if($$pkf==$ctid){
			$$pkf=$ctid==0?$#$pstats:$$pkf-1;
		    }elsif($$pkf+1==$ctid){
			$$pkf++;
		    }elsif($$pkf==$#$pstats&&$ctid==0){
			$$pkf=0;
		    }
		    &set_ctid(&$pmv(-1,$ctid),1);
		}
	    }

	    when($Gtk2::Gdk::Keysyms{Delete}){
		my $nkf;
		my ($k1,$k2);
		unless(defined $selk){
		    $k1=$k2=$ctid;
		}else{
		    ($k2,$k1)=($lensel>=0)?($selk,$selk+$lensel)
			:($selk+$lensel,$selk);
		}
		for(my $k=$k1;$k>=$k2;$k--){
		    &$prm($k);
		}
		if($$pkf>=$k2&&$$pkf<=$k1){
		    $nkf=$k2; # $kf will be removed and show next
		}elsif($$pkf>$k1){
		    $nkf=$$pkf-($k1-$k2+1); # $kf not removed and need to slide index
		}
		&$pshow_image($nkf,1);
		undef $selk;
		&set_ctid($k2,1);
	    }

	    when($Gtk2::Gdk::Keysyms{plus}){
		$scale+=$$pdscale*($shft?2:1);
		&rescale;
	    }
	    when($Gtk2::Gdk::Keysyms{minus}){
		my $d=$$pdscale*($shft?2:1);
		$scale-=$scale>$d?$d:0;
		&rescale;
	    }

	    when($Gtk2::Gdk::Keysyms{space}){
		if(defined $selk){
		    $selk=-1; # <<<
		}else{
		    unless(substr($pstats->[$ctid],0,1,"1")){
			$$pnsel++;
		    }elsif(substr($pstats->[$ctid],0,1,"0")){
			$$pnsel--;
		    }
		}
		&set_ctid();
	    }

	    when($Gtk2::Gdk::Keysyms{Return}){
		&$pshow_image($ctid,1);
	    }
	    when($Gtk2::Gdk::Keysyms{i}){
		$lstaon=$lstaon==2?0:$lstaon+1;
		if($lstaon==0){
		    if($sbox){
			$sbox->hide;
		    }else{
			$lsta->hide;
		    }
		}else{
		    if($sbox){
			$sbox->show;
		    }else{
			$lsta->show;
		    }
		    &set_ctid();
		}
	    }
	    when($Gtk2::Gdk::Keysyms{I}){
		$gidon=$gidon==3?0:$gidon+1;
		$twin->queue_draw;
	    }
	    when($Gtk2::Gdk::Keysyms{G}){
		&set_ctid($#$pstats);
	    }
	    when($Gtk2::Gdk::Keysyms{g}){
		&set_ctid(0);
	    }

	    when($Gtk2::Gdk::Keysyms{a}){
		$$pA=(defined($$pA)&&$$pA==$ctid)?undef:$ctid;
	    }
	    when($Gtk2::Gdk::Keysyms{b}){
		$$pB=(defined($$pB)&&$$pB==$ctid)?undef:$ctid;
	    }

	    when($Gtk2::Gdk::Keysyms{backslash}){
		&$ppop($ctid);
	    }

	    when($Gtk2::Gdk::Keysyms{equal}){
		if($#$pstats>1){
		    my $r=0;
		    my ($w,$h);
		    my $n=0;
		    &$powh;
		    foreach(@$pstats){
			($w,$h)=($1,$2) if $_=~$owhqr;
			next unless $h&&$w;
			$r+=$w/$h;
			$n++;
		    }
		    $r/=$n;
		    $$pasp=$r;
		    $thumh0=int($thumw0/($r>0?$r:1));
		    ($thumw,$thumh)=($thumw0*$scale,$thumh0*$scale);
		    ($ftw,$fth)=($thumw+2*$xgap,$thumh+2*$ygap);
		    undef @$pthums;
		    $twin->queue_draw;
		}
	    }

	    when ( $Gtk2::Gdk::Keysyms{f}){
		return FALSE unless $win;
		my $f=$fullscreen;
		$fullscreen?$win->unfullscreen:$win->fullscreen;
		$fullscreen=!$fullscreen if($fullscreen==$f); # <<< ws failed
	    }

	    when ( $Gtk2::Gdk::Keysyms{M}){
		return FALSE unless $win;
		my $m=$maximized;
		$maximized?$win->unmaximize:$win->maximize;
		$maximized=!$maximized if($maximized==$m); # <<< ws failed
	    }
	    when ( $Gtk2::Gdk::Keysyms{F} ){
		return FALSE unless $win;
		my $a=$above;
		$win->set_keep_above($above?FALSE:TRUE);
		$above=!$above if($a==$above); # ws_event failed
	    }

	    when($Gtk2::Gdk::Keysyms{p}){
		undef @$pthums;
		&set_ctid(undef,1);
	    }

	    when($Gtk2::Gdk::Keysyms{bar}){
		&detach_tnv(1);
		my $s;
		unless($win){
		    $s=$scrtype+(($scrtype<=2)?2:-2);
		}else{
		    $s=(($scrtype==4)?1:$scrtype+1);
		}
		&mk_tnv($s);
		$vbox->show_all;
		$twin->grab_focus;
	    }

	    when($Gtk2::Gdk::Keysyms{q}){
		if($win){
		    $win->destroy;
		}else{
		    &$pfin;
		}
		return TRUE; # must not call &sbar
	    }
	    default{return FALSE;}
	}
    }elsif($ctrl&&!$meta){
	given($_[0]){
	    when ( $Gtk2::Gdk::Keysyms{z} ){
		return FALSE unless $win;
		$win->iconify;
	    }
	    when($Gtk2::Gdk::Keysyms{plus}){
		$fontw+=($shft?4:2); #<<<
		$twin->queue_draw;
	    }
	    when($Gtk2::Gdk::Keysyms{minus}){
		my $d=($shft?4:2); #<<<
		$fontw-=$fontw>$d?$d:0;
		$twin->queue_draw;
	    }
	    default{return FALSE;}
	}
    }else{
	return FALSE;
    }

    &$psbar;
    return TRUE;
}

sub rescale
{
    my ($w,$h)=($thumw0*$scale,$thumh0*$scale);
    return if($w<32&&$h<32); # <<<<<<<<<<<<<<<
    ($thumw,$thumh)=($w,$h);
    ($ftw,$fth)=($thumw+2*$xgap,$thumh+2*$ygap);
    $fontw=$ftw/8; #<<<<<<<<
    @$pthums=();
    my ($x,$y);
    if($scrtype & 1){
	($x,$y)=(0,$ypos);
    }else{
	($x,$y)=($xpos,0);
    }
    ($w,$h)=$twin->window->get_size;
    &configure_cb; # <<<<<<
    $twin->queue_draw;
}

sub set_ctid
{
    #my $ntid,$update_whole,$keyevent=@_
    $ctid=0 unless defined $ctid;
    $ctid=$#$pstats if $ctid>$#$pstats;
    &set_ps if($nfold!=@$pstats||!$$pn1);
    $nfold=@$pstats;
    return unless $$pn1;
    my ($xc,$yc)=&k2p($ctid) unless defined $_[1];
    unless(defined $_[0]){
	unless(defined $_[1]){
	    $twin->queue_draw_area($xc,$yc,$ftw,$fth);
	}else{
	    $twin->queue_draw;
	}
	goto FIN;
    }
    my $ntid=$_[0];
    my $warp=1;
    if($ntid<0){
	$ntid=$#$pstats;
    }elsif($ntid>=@$pstats){
	$ntid=0;
    }else{
	$warp=0;
    }

    my $cle=defined $_[1]?$_[1]:0;
    my $d=$ctid-$ntid;

    if(!defined($selk) && $shft && defined $_[2]){
	$selk=$ctid;
	$lensel=0;
    }

    if(defined $selk){
	my $pset=sub{
	    for($_[0]..$_[1]){
		$$pnsel++ unless substr($pstats->[$_],0,1,"1");
	    }
       	};
	my $pcle=sub{
	    for($_[0]..$_[1]){
		$$pnsel-- if substr($pstats->[$_],0,1,"0");
	    }
       	};

	if(!$shft||$warp){ # warp clear selection
	    if($selk>=0){
		my ($k1,$k2)=($lensel>=0)?($selk,$selk+$lensel)
		    :($selk+$lensel,$selk);
		$pcle->($k1,$k2);
		$cle=1;
	    }
	    undef $selk;
	    undef $lensel;
	}else{
	    my $ls=$ntid-$selk;
	    if($ls>0){
		$pset->($selk,$ntid);
	    }elsif($ls<0){
		$pset->($ntid,$selk);
	    }
	    if($lensel<$ls&&$ls<=0){
		$pcle->($lensel-$ls+$ntid,-1+$ntid);
	    }elsif($lensel>$ls&&$ls>=0){
		$pcle->(1+$ntid,$lensel-$ls+$ntid);
	    }
	    $lensel=$ls;
	    $cle=1 if(abs($d)>1);
	}
    }

    ($x,$y)=&k2p($ntid); # for clamp_page
    $ctid=$ntid;
    unless($cle){
	$twin->queue_draw_area($xc,$yc,$ftw,$fth);
	$twin->queue_draw_area($x,$y,$ftw,$fth) if $d;
    }else{
	$twin->queue_draw;
    }
    $adj->clamp_page($$py+$$zpos,$$py+$$zpos+$$pft2);
  FIN:
    &set_lsta if $lsta->visible;
}

sub set_lsta
{
    my $f=&$pgetfn($ctid);
    if($f){
	$$pstats[$ctid]=~/^\d:(\d+)/;
	my $g=$1;
	$f=~s/.*\/// if $lstaon==1;
	$f="[".($ctid+1)."/".scalar(@$pstats).($$pnsel>0?":$$pnsel":"")
	    ."]".($g?"\@$g ":" ").$f;
    }else{
	$f="";
    }
    $lsta->set_text($f);
}

sub wheel_cb
{
    my $d = ($_[1]->get_state>=['shift-mask'])?$adj->page_increment:$adj->step_increment;
    $d=-$d if $_[1]->direction eq "up";
    $adj->set_value($adj->value+$d);
    return FALSE;
}

sub scroll_cb
{
    my $z=int $adj->value;
    return unless defined $$zpos;
    return if $z==$$zpos;
    ($x,$y)=(0,$$zpos-$z);
    $twin->window->scroll($$px,$$py);
    $$zpos=$z;
}

sub quit_idle
{
    Glib::Source->remove($icbid);
    undef $icbid;
    undef @idleq;
    $wqid=-1;
}

sub set_ps
{
    my $d=($twin->window->get_size)[$s1];
    return if($d<=1);

    my $ini=$$pn1;
    $$pn1=int($d/$$pft1)||1;
    $$pn1++ if $$pft1*($$pn1+1)-$d<=$$pft1*1/4; # <<<
    $$pn2=int(@$pstats/$$pn1);
    $$pn2++ if $$pn1*$$pn2<@$pstats;

    my ($ps,$up);
    $ps=($twin->window->get_size)[$s2];
    $up=$$pn2*($$pft2);

    $adj->page_size($ps);
    $adj->step_increment($ps*.125);
    $adj->page_increment($ps*.75);
    if($ps>=$up){
	$scr->hide; $adj->upper(0);
    }else{
	$scr->show; $adj->upper($up);
	my $uv=$up-$ps;
	$adj->value($uv) if $adj->value>$uv;
    }

    unless(defined $ini){ # the first timw
	$$zpos=$ctid/$$pn1*$$pft2;
	$adj->set_value($$zpos);
    }
}

#sub rgb2hsv
#{
#    my ($min,$v,$maxk)=(10,-1,-1);
#    for(my $k=0;$k<3;$k++){
#	$min=$_[$k] if $min>$_[$k];
#	if($v<$_[$k]){
#	    $v=$_[$k];
#	    $maxk=$k;
#	}
#    }
#    my $s=0;
#    $s=($v-$min)/$v if $v>0;
#    my $h=0;
#    if($v!=$min){
#	my ($a,$b);
#	given($maxk){
#	    when(0){
#		$a=$_[1]-$_[2];
#		$b=0;
#	    }
#	    when(1){
#		$a=$_[2]-$_[0];
#		$b=120;
#	    }
#	    when(2){
#		$a=$_[0]-$_[1];
#		$b=240;
#	    }
#	}
#	$h=60*$a/($v-$min)+$b;
#	$h+=360 if $h<0;
#    }
#    return ($h,$s,$v);
#}
#
#sub hsv2rgb
#{
#    return ($_[2],$_[2],$_[2]) if $_[1]==0;
#    my $f=$_[0]/60;
#    my $hi=int($f);
#    $f=$f-$hi;
#    $hi=$hi % 6;
#    my $p=$_[2]*(1-$_[1]);
#    my $q=$_[2]*(1-$_[1]*$f);
#    my $t=$_[2]*(1-$_[1]*(1-$f));
#    given($hi){
#	return ($_[2],   $t,    $p) when(0);
#	return ($q,   $_[2],    $p) when(1);
#	return ($p,   $_[2],    $t) when(2);
#	return ($p,      $q, $_[2]) when(3);
#	return ($t,      $p, $_[2]) when(4);
#	return ($_[2],   $p,    $q) when(5);
#    }
#}

sub configure_cb
{
    &quit_idle if defined $icbid;

    unless($stdgc){
	my $mysgc=$twin->style->base_gc("selected");
	unless($sbgc){
	    $selgc=$mysgc;
	}else{
	    $selgc=Gtk2::Gdk::GC->new($twin->window);
	    $selgc->set_rgb_fg_color($sbgc);
	}
	my $pv=$mysgc->get_values;
	$selgc->set_line_attributes (2,
				     $$pv{"line_style"}, $$pv{"cap_style"}, $$pv{"join_style"});
    	my $fg=defined($sbgc)?$sbgc:$$pv{"foreground"};
	my $col=Gtk2::Gdk::Color->new(65535-$fg->red,65535-$fg->green,65535-$fg->blue);
	$invselgc=Gtk2::Gdk::GC->new($twin->window);
	$invselgc->set_rgb_fg_color($col);
	$invselgc->set_line_attributes (2,
				     $$pv{"line_style"}, $$pv{"cap_style"}, $$pv{"join_style"});
		unless(defined $bgc){
	    $stdgc=$twin->style->base_gc("insensitive");
	}else{
	    $stdgc=Gtk2::Gdk::GC->new($twin->window);
	    $stdgc->set_rgb_fg_color($bgc);
	}
    }
    &set_ps;
    if(!defined($win)){
	&$pconfig_cb(undef,undef,
		     $vbox->allocation->width,
		     $vbox->allocation->height);
    }
}

sub expose_cb
{
    my ($w,$e)=@_;
    my ($x1,$y1,$x2,$y2);
    $$zpos=int($adj->value);
    if($scrtype & 1){
	($x1,$y1,$x2,$y2)=$e->area->values;
    }else{
	($y1,$x1,$y2,$x2)=$e->area->values;
    }
    $y1+=$$zpos;
    $x2+=$x1; $y2+=$y1;

    my $k0=int(($y1-$$ptd2)*$$pn1/$$pft2); #<<<<<<<<<<<<<<<<<<<<<<<<<
    $k0=0 if($k0<0);
    for(my $k=$k0;$k<=$#$pstats;$k++){
	my ($ix,$iy,$x,$y);
	$iy=int($k/$$pn1);
	$y=$$pft2*$iy;
	last if $y2<=$y;
	next if $y+$$ptd2<=$y1;
	$ix=$k-$iy*$$pn1;
	$x=$$pft1*$ix;
	next if $x+$$ptd1<=$x1;
	next if $x2<=$x;
	push @idleq, [$k,$x,$y]; # twisted
	$icbid||=Glib::Idle->add(\&idle);
    }
    1;
}

sub idle
{
    return FALSE unless defined $twin->window; # <<<<<< ++O:C-Escape makes it happen
    require "Cairo.pm";
    if(++$wqid>$#idleq){
	undef $icbid;
	undef @idleq;
	$wqid=-1;
	return FALSE;
    }
    {
	if($gidon&&$idleq[$wqid]->[0]==$ctid&&$wqid+1<=$#idleq&&$idleq[$wqid+1]->[0]==$ctid+1){
	    # the next thumb might overwrite current osd
	    ($idleq[$wqid],$idleq[$wqid+1])=($idleq[$wqid+1],$idleq[$wqid]);
	}
	my $pq=$idleq[$wqid];
	my $k;
	($k,$xx,$yy)=@$pq; # twisted
	$yy-=$$zpos;
	last if(($scrtype & 1)
		&&($yy>($twin->window->get_size)[$s2]||$yy+$pft2-$$zpos<0));
	my $f=&$pgetfn($k);

	unless($pthums->[$k]){
	    eval{
		# fails on corrupted jpg or gif
		$pthums->[$k]=Gtk2::Gdk::Pixbuf->new_from_file_at_scale(
		    $f, $thumw, $thumh,1);
	    };
	    if($@){
		print "Gtk2::Gdk::Pixbuf->new_from_file_at_scale failed: $f\n";
		print $@,"\n";
		my $p=&$pngp;
		last unless $p;
		$pthums->[$k]=$p->scale_simple($thumw,$thumh,"hyper");
	    }
	}

	my $x0=int(($thumw-$pthums->[$k]->get_width )/2)+$xgap;
	my $y0=int(($thumh-$pthums->[$k]->get_height)/2)+$ygap;

	# twisted & twisted = direct

	my $gc=$selgc;
	my $fill=1;
	my ($x1,$y1)=($$pxx+1,$$pyy+1);
	my ($w,$h)  =($ftw-2,$fth-2);
	my $s=substr($pstats->[$k],0,1);
	my $waku=0;
	if($k==$ctid){
	    unless($s){
		$x1 += 1;
		$y1 += 1;
		$w  -= 2;
		$h  -= 2;
		$fill= 0;
	    }else{
		$waku=1;
	    }
	}elsif(!$s){
	    $gc=$stdgc;
	}

	$twin->window->draw_rectangle($gc,$fill,
				      $x1,$y1,
				      $w,$h);

	$twin->window->draw_pixbuf($stdgc,$pthums->[$k],0,0,
			   $$pxx+$x0, $$pyy+$y0,-1,-1,'none',0,0);

	if($waku){
	    $twin->window->draw_rectangle($invselgc,0,
					  $x1+1,$y1+1,
					  $w-2,$h-2);
	}

	if($gidon){
	    my $gid="?";
	    my @fc=(1,1,1);
	    if($gidon==1){
		$gid=$k+1;
	    }elsif($gidon==2){
		($gid=$pfiles->[$k])=~s/$didqr//;
	    }elsif($pstats->[$k]=~$gidqr&&$1>0){
		$gid="@".$1;
		@fc=(0.8,0.8,1.0);
	    }
	    my $cr=Gtk2::Gdk::Cairo::Context->create($twin->window);
	    #
	    $cr->select_font_face($$pfont,$s?"italic":"normal",$k==$ctid?"bold":"normal");
	    $cr->set_font_size($fontw);
	    my $m=int($fontw*(0.4)); #<<<
	    $m++ if $m&1;
	    my ($tw,$th,$bx,$by)=($cr->text_extents($gid)->{"width"},
				  $cr->text_extents($gid)->{"height"},
				  $cr->text_extents($gid)->{"x_bearing"},
				  $cr->text_extents($gid)->{"y_bearing"});
	    my ($rw,$rh)=($tw+$m,$th+$m);
	    #
	    $cr->set_source_rgba(0,0,0,0.5);
	    my $r=$m;
	    my $deg=.01745329251994329576923690768488;
	    $cr->new_sub_path();
	    $cr->arc($x1+$rw-$r, $y1+$r,     $r, -90*$deg,   0);
	    $cr->arc($x1+$rw-$r, $y1+$rh-$r, $r,   0,       90*$deg);
	    $cr->arc($x1+$r,     $y1+$rh-$r, $r,  90*$deg, 180*$deg);
	    $cr->arc($x1+$r,     $y1+$r,     $r, 180*$deg, 270*$deg);
	    $cr->close_path();
	    $cr->fill;
	    #
	    $cr->move_to($x1-$bx+$m/2,$y1-$by+$m/2);
	    $cr->set_source_rgb(@fc);#1,1,1);
	    $cr->show_text($gid);
	}
    }
    return TRUE;
}

sub motion_notify_event
{
    if($clicked){
	$isdrag=1 unless $isdrag;
	$adj->set_value($apos-(($scrtype & 1)?$_[1]->y_root:$_[1]->x_root)) if defined $apos;
	return TRUE;
    }
}

sub butpre_cb
{
    $twin->grab_focus;
    $clicked=$_[1]->button;
    $apos=$adj->value+(($scrtype & 1)?$_[1]->y_root:$_[1]->x_root) if $scr->visible;
    return TRUE;
}

sub butrel_cb
{
    undef $clicked;
    undef $apos;
    unless($isdrag){
	($x,$y)=$_[1]->get_coords;
	my $k=&p2k;
	return FALSE unless defined $k;
	given($_[1]->button){
	    when(1){
		&$pshow_image($k,1);
		&set_ctid($k);
		&$psbar();
	    }
	    when(3){
		my ($xc,$yc)=&k2p($k);
		unless(substr($pstats->[$k],0,1,"1")){
		    $$pnsel++;
		}elsif(substr($pstats->[$k],0,1,"0")){
		    $$pnsel--;
		}
		$twin->queue_draw_area($xc,$yc,$ftw,$fth);
	    }
	    when(2){
		&$ppop($k);
	    }
	    default{return FALSE;}
	}
    }
    undef $isdrag;
    return TRUE;
}

sub ws_event
{
    my ($w,$e) = @_;
    if ( $e->changed_mask & "maximized" ){
	if ( $e->new_window_state & "maximized" ){
	    $maximized = 1;
	}else{
	    $maximized = 0;
	}
    }elsif ( $e->changed_mask & "fullscreen" ){
	if ( $e->new_window_state & "fullscreen" ){
	    $fullscreen = 1;
	}else{
	    $fullscreen = 0;
	}
    }elsif ( $e->changed_mask & "above" ){ # doesnotwork
	if ( $e->new_window_state & "above" ){
	    $above = 1;
	}else{
	    $above = 0;
	}
#    }else{
#	&echo "ws_event";
#	return;
    }
}

sub p2k
{
#    ($x,$y)=@_; # direct in
    ($$px,$$py)=(int($$px/($$pft1)),int(($$py+$$zpos)/($$pft2)));
    return ($$px>=$$pn1)?undef:$$py*$$pn1+$$px # twisted out
}

sub k2p
{
    my $k=$_[0];
    $y=int($k/$$pn1); # twisted eval
    $x=$k-$y*$$pn1;
    ($x,$y)=($x*$$pft1,$y*$$pft2);
    $y-=$$zpos;
    return ($$px,$$py); # direct out
}

1;
